The stivale2 boot protocol is an improved version of the stivale protocol which provides the kernel with most of the features one may need in a modern x86_64 context (although 32-bit x86 is also supported).
In order to have a stivale2 compliant kernel, one must have a kernel executable,
in the elf64
or elf32
format, with a .stivale2hdr
section (described below), or
a non-ELF kernel providing a valid stivale2 anchor (see below).
Other executable formats are not supported.
stivale2 will recognise whether the ELF file is 32-bit or 64-bit and put the CPU into the appropriate mode before loading the kernel. In the case of anchored kernels, the anchor provides information about which CPU mode to use.
stivale2 natively supports (only for 64-bit ELF kernels) and encourages higher half kernels.
The kernel can load itself at or above 0xffffffff80000000
(as defined in the linker script)
and the bootloader will take care of everything else, no AT linker script directives needed.
For relocatable kernels, the bootloader may change the load addresses of the sections by adding a slide if the specified location of a segment is not available for use. This slide will never have a value not aligned to at least the same alignment as the ELF segment with the largest alignment.
If KASLR is enabled, a random slide, still maintaining the above alignment constraint, will be added unconditionally.
If the kernel loads itself in the lower half, the bootloader will not perform the higher half relocation and protected memory ranges (PMRs) will not be available.
See the protected memory ranges section for more information on those.
An anchored kernel is a non-ELF kernel, of unspecified executable or otherwise format, which provides an anchor for the bootloader to latch onto in order to gather information needed to perform kernel loading and handoff.
Anchored kernels are useful for kernels in a.out, flat binary, or otherwise non-ELF format that support direct loading.
PMRs and higher half relocation will not be available for anchored kernels.
The stivale2 anchor can appear anywhere in the executable, but must be aligned on a 16-byte boundary. Only the firstmost anchor that appears in the file is used. It must be filled in as follows:
struct stivale2_anchor {
uint8_t anchor[15]; // Must be ASCII sequence "STIVALE2 ANCHOR"
// (excluding quotes)
uint8_t bits; // What mode to be handed off control in: 32 or 64
uint64_t phys_load_addr; // Physical address to load the kernel executable to
uint64_t phys_bss_start; // Physical address of beginning of bss section
uint64_t phys_bss_end; // Physical address of end of bss section
uint64_t phys_stivale2hdr; // Physical address of stivale2 header after kernel is
// loaded in memory
};
Anchored kernels are otherwise functionally equivalent to ELF counterparts.
rip
will be the entry point as defined in the ELF file, unless the entry_point
field in the stivale2 header is set to a non-0 value, in which case, it is set to
the value of entry_point
. For anchored kernels, the entry_point
field of the
header is always utilised.
At entry, the bootloader will have setup paging mappings as such:
Base Physical Address - Size -> Virtual address
0x0000000000000000 - 4 GiB plus any additional memory map entry -> 0x0000000000000000
0x0000000000000000 - 4 GiB plus any additional memory map entry -> HHDM start
0x0000000000000000 - 0x80000000 -> 0xffffffff80000000 (No PMRs only)
Where HHDM start is returned in the Higher Half Direct Map tag (see below).
All the mappings are supervisor, read, write, execute (-rwx).
If PMRs are requested, the bottom-most of the above mappings (the one marked as
(No PMRs only)
) will not be created, and in its place, only mappings
that describe the kernel's loaded ELF segments with appropriately set MMU permissions
will be created. For relocatable kernels, slides will be appropriately taken into
account. PMRs are not available to lower half or anchored kernels.
If the "unmap NULL" header tag is specified, then page 0, or the first 4KiB of the virtual address space, is going to be unmapped.
The bootloader page tables are in bootloader-reclaimable memory, their specific layout is undefined as long as they provide the above memory mappings.
If the kernel is a position independent executable, the bootloader is free to relocate it as it sees fit, potentially performing KASLR (as specified by the config).
At entry all segment registers are loaded as 64 bit code/data segments, limits and bases are ignored since this is 64-bit mode.
The GDT register is loaded to point to a GDT, in bootloader-reserved memory, with at least the following entries, starting at offset 0:
- Null descriptor
- 16-bit code descriptor. Base =
0
, limit =0xffff
. Readable. - 16-bit data descriptor. Base =
0
, limit =0xffff
. Writable. - 32-bit code descriptor. Base =
0
, limit =0xffffffff
. Readable. - 32-bit data descriptor. Base =
0
, limit =0xffffffff
. Writable. - 64-bit code descriptor. Base and limit irrelevant. Readable.
- 64-bit data descriptor. Base and limit irrelevant. Writable.
The IDT is in an undefined state. Kernel must load its own.
IF flag, VM flag, and direction flag are cleared on entry. Other flags undefined.
PG is enabled (cr0
), PE is enabled (cr0
), PAE is enabled (cr4
),
LME is enabled (EFER
).
If the stivale2 header tag for 5-level paging is present, then, if available,
5-level paging is enabled (LA57 bit in cr4
).
If PMRs are requested, then, the NX bit will be enabled (NX bit in EFER
).
The A20 gate is opened.
Legacy PIC and IO APIC IRQs are all masked.
If booted by EFI/UEFI, boot services are exited.
rsp
is set to the requested stack as per stivale2 header. If the requested value is
non-null, an invalid return address of 0 is pushed to the stack before jumping
to the kernel.
rdi
will contain the address of the stivale2 structure (described below).
All other general purpose registers are set to 0.
eip
will be the entry point as defined in the ELF file, unless the entry_point
field in the stivale2 header is set to a non-0 value, in which case, it is set to
the value of entry_point
. For anchored kernels, the entry_point
field of the
header is always utilised.
At entry all segment registers are loaded as 32 bit code/data segments.
All segment bases are 0x00000000
and all limits are 0xffffffff
.
The GDT register is loaded to point to a GDT, in bootloader-reserved memory, with at least the following entries, starting at offset 0:
- Null descriptor
- 16-bit code descriptor. Base =
0
, limit =0xffff
. Readable. - 16-bit data descriptor. Base =
0
, limit =0xffff
. Writable. - 32-bit code descriptor. Base =
0
, limit =0xffffffff
. Readable. - 32-bit data descriptor. Base =
0
, limit =0xffffffff
. Writable. - 64-bit code descriptor. Base and limit irrelevant. Readable.
- 64-bit data descriptor. Base and limit irrelevant. Writable.
The IDT is in an undefined state. Kernel must load its own.
IF flag, VM flag, and direction flag are cleared on entry. Other flags undefined.
PE is enabled (cr0
).
The A20 gate is enabled.
Legacy PIC and IO APIC IRQs are all masked.
If booted by EFI/UEFI, boot services are exited.
esp
is set to the requested stack as per stivale2 header. An invalid return address
of 0 is pushed to the stack before jumping to the kernel.
The address of the stivale2 structure (described below) is pushed onto this stack before the entry point is called.
All other general purpose registers are set to 0.
IP
will be the entry point as defined in the ELF file, unless the entry_point
field in the stivale2 header is set to a non-0 value, in which case, it is set to
the value of entry_point
.
At entry, the bootloader will have setup paging mappings as such:
Base Physical Address - Size -> Virtual address
0x0000000000000000 - 4 GiB plus any additional memory map entry -> 0x0000000000000000
0x0000000000000000 - 4 GiB plus any additional memory map entry -> HHDM start
Where HHDM start is returned in the Higher Half Direct Map tag (see below).
If the kernel is dynamic and not statically linked, the bootloader will relocate it, potentially performing KASLR (as specified by the config).
The kernel should NOT modify the bootloader page tables, and it should only use them to bootstrap its own virtual memory manager and its own page tables.
The kernel is entered at EL1.
VBAR_EL1
is in an undefined state. Kernel must load its own.
DAIF.{D,A,I,F}
are set/masked.
SCTLR_EL1.{M,C,I}
are set. Other bits are undefined.
MAIR
has at least the following entries, in some order:
0b00001100
, used for mmio regions0b11111111
, used for normal memory
SP
is set to the requested stack as per stivale2 header.
The LR
register has an invalid return address.
SPSel.SP
is 0.
Neither floating point, SIMD nor timer accesses trap to a higher EL than 1.
X0
will contain the physical address of the stivale2 structure (described below).
All other general purpose registers are undefined.
THIS IS DEPRECATED, DO NOT USE. USE THE MEMORY MAP FOR THIS. TO PREVENT THE BOOTLOADER FROM REFUSING TO BOOT IN CASE THIS AREA IS NOT AVAILABLE SET BIT 4 OF THE HEADER FLAGS!
For x86_64 and IA-32, stivale2 guarantees that an area of no less than 32 KiB is
free and usable at physical memory address 0x70000
, regardless of what is
specified in the memory map.
This allows the kernel to perform tasks that require Real Mode accessible memory such as multicore startup.
In order for stivale2 to function, it needs to reserve memory areas for either internal usage (such as page tables, GDT, SMP), or for kernel interfacing (such as returned structures).
stivale2 ensures that none of these areas are found in any of the sections marked as "usable" or "kernel/modules" in the memory map.
The location of these areas may vary and it is implementation specific, but they are guaranteed to be in "bootloader reclaimable" entries.
Once the OS is done needing the bootloader, memory map areas marked as "bootloader reclaimable" may be used as usable memory. These areas are guaranteed to be aligned to the smallest possible page size (4K on x86_64 and IA-32), for both base and length, and they are guaranteed to not overlap other sections of the memory map.
The kernel executable shall have an ELF section named .stivale2hdr
which will
contain the following header; or an anchor pointing to the following header for
anchored kernels.
Said header looks like this:
struct stivale2_header {
uint64_t entry_point; // If not 0, this address will be jumped to as the
// entry point of the kernel.
// If set to 0, the ELF entry point will be used
// instead.
uint64_t stack; // This is the stack address which will be in ESP/RSP
// when the kernel is loaded.
// It can only be set to NULL for 64-bit kernels. 32-bit
// kernels are mandated to provide a vaild stack.
// 64-bit and 32-bit valid stacks must be at least 256 bytes
// in usable space and must be 16 byte aligned addresses.
uint64_t flags; // Bit 0: Formerly used to indicate whether to enable
// KASLR, this flag is now reserved as KASLR
// is enabled in the bootloader configuration
// instead. Presently reserved and unused.
// Bit 1: If set to 1, all pointers, except otherwise noted,
// are to be offset to the higher half. That is,
// their value will be their physical address plus
// 0xffff800000000000 with 4-level paging or
// 0xff00000000000000 with 5-level paging on x86_64.
// Success for this feature can be tested by checking
// whether the stivale2 struct pointer argument passed
// to the entry point function is in the higher
// half or not.
// Bit 2: If set to 1, enables protected memory ranges.
// See the relevant section.
// Bit 3: If set to 1, enables fully virtual kernel mappings
// for PMRs. Only works if PMRs are enabled.
// Bit 4: Do NOT fail to boot if low memory area could not be
// allocated. THIS BIT SHOULD ALWAYS BE SET AS THIS
// FUNCTIONALITY IS DEPRECATED.
// All other bits are undefined and must be 0.
uint64_t tags; // Pointer to the first tag of the linked list of
// header tags.
// See "stivale2 tags" section.
// The pointer can be either physical or, for higher
// half kernels, the virtual in-kernel address.
// NULL = no tags.
};
Enabling protected memory ranges (bit 2 in the flags
field of the main header
struct) tells the stivale2-compliant bootloader that the kernel wants the top-most
2 GiB of the address space to be mapped as per its ELF segments, rather than as a
monolithic, all-permissions, block mapped from physical address 0.
Only 64-bit, higher half, ELF (non anchored) kernels can take advantage of this feature. Requesting this feature for 32-bit, lower half, or non-ELF kernels has undefined behaviour.
For PMRs on the x86_64 platform, non-readable ranges are not possible, therefore they are ignored and forced readable in the MMU, but they are still reported back to the kernel in the struct tag.
If the ELF file contains segments whose virtual address is not at or above
0xffffffff80000000
, they will be ignored and not loaded.
Bit 3 of the flags field enables fully virtual kernel mappings. This means that
there no longer is an enforced PHYSICAL_ADDRESS = VIRTUAL_ADDRESS - 0xffffffff80000000
correspondence for kernel image addresses, unlike when this bit is off.
This bit is relevant in order to ensure load is always possible even if the otherwise corresponding physical address is not available, as the bootloader is allowed to pick any other physical load address, even without requiring the kernel to be relocatable or position independent. For KASLR, this also ensures there is a wider range of available slides even if physical memory would not otherwise suffice.
It is recommended to always enable this feature, whenever possible.
When this feature is requested, and the bootloader supports it, the corresponding kernel base address struct tag (see below) is returned.
The main stivale2 structure returned by the bootloader looks like this:
struct stivale2_struct {
char bootloader_brand[64]; // 0-terminated ASCII bootloader brand string
char bootloader_version[64]; // 0-terminated ASCII bootloader version string
uint64_t tags; // Address of the first of the linked list of struct tags.
// see "stivale2 tags" section.
// NULL = no tags.
};
stivale2 uses a mechanism to avoid having protocol versioning, but rather, feature-specific support detection.
This mechanism is tags. Each tag is a structure which shall begin with these 2 members, represented by the following structure:
struct stivale2_tag {
uint64_t identifier;
uint64_t next;
};
The identifier
field identifies what feature the tag is requesting from the
bootloader.
The next
field points to another tag in the linked list. A NULL value determines
the end of the linked list. For header tags (kernel to bootloader), just like the
tags
pointer in the stivale2 header, this value can either be a physical address or,
for higher half kernels, the virtual in-kernel address. For struct tags (bootloader
to kernel), this can either be a physical or a higher half direct map address, as
requested by the kernel in the main header.
Tag structures can have more than just these 2 members, but these 2 members MUST appear at the beginning of any given tag.
Tags can have no extra members and just serve as "flags" to enable some behaviour that does not require extra parameters.
The kernel executable provides the bootloader with a linked list of tag structures,
the first of which is pointed to by the tags
entry of the stivale2 header.
These are called "header tags".
The bootloader is free to ignore kernel tags that it does not recognise. The kernel should make sure that the bootloader has properly interpreted the provided tags, either by checking returned tags or by other means.
Likewise, the bootloader provides the kernel with another linked list of tag structures, the root of which can be found in the main stivale2 structure, passed to the kernel by platform specific means (see above). These are called "struct tags".
The kernel is responsible for parsing the tags and the identifiers, and interpreting the tags that it supports, while handling in a graceful manner the tags it does not recognise.
This tag tells the stivale2-compliant bootloader that the kernel has no requirement for a framebuffer to be initialised.
Depending on the preference
field, the kernel can either tell the bootloader that
it prefers a graphical linear framebuffer, but the lack of one (or CGA text mode) is also
acceptable (value 0
); or that it prefers the lack of one (or CGA text mode), but
if such is unavailable, a graphical linear framebuffer is also acceptable (value 1
).
Omitting both the any video header tag and the framebuffer header tag means "force CGA text mode" (where available), and the bootloader will refuse to boot the kernel if it fails to fulfill that request.
To detect which mode the bootloader ends up picking, either the framebuffer structure tag or the text mode structure tag (only one of them, see below) is returned to the kernel.
struct stivale2_header_tag_any_video {
struct stivale2_tag tag; // Identifier: 0xc75c9fa92a44c4db
uint64_t preference; // 0: prefer linear framebuffer
// 1: prefer no linear framebuffer
// (CGA text mode if available)
// All other values undefined.
};
This tag can be used alongside the "any video" header tag to specify further framebuffer preferences.
If used alone, without "any video" header tag, this tag mandates a graphical linear framebuffer and, if the tag is supported, the bootloader will refuse to boot the kernel if it fails to initialise a framebuffer. If this tag is not supported by the bootloader, it will boot the kernel anyways, therefore it is the kernel's responsibility to verify that a framebuffer struct tag was returned.
The width, height, and bpp fields are just hints for the preferred resolution, but the bootloader reserves the right to override these values if no such video mode is available.
struct stivale2_header_tag_framebuffer {
struct stivale2_tag tag; // Identifier: 0x3ecc1bc43d0f7971
uint16_t framebuffer_width; // If all values are set to 0
uint16_t framebuffer_height; // then the bootloader will pick the best possible
uint16_t framebuffer_bpp; // video mode automatically.
uint16_t unused;
};
Note: This tag is deprecated and considered legacy. Use is discouraged and it may not be supported on newer bootloaders.
The presence of this tag tells the bootloader to, in case a framebuffer was requested, make that framebuffer's caching type write-combining using x86's MTRR model specific registers. This caching type helps speed up framebuffer writes on real hardware.
It is recommended to use this tag in conjunction with the SMP tag in order to let the bootloader make the MTRR settings uniform across all CPUs.
If no framebuffer was requested, this tag has no effect.
Identifier: 0x4c7bb07731282e00
This tag does not have extra members.
If this tag is present the bootloader is instructed to set up a terminal for use by the kernel at runtime. See "Terminal struct tag" below.
The terminal can run in either framebuffer or text mode, depending on the target video mode requested.
struct stivale2_header_tag_terminal {
struct stivale2_tag tag; // Identifier: 0xa85d499b1823be72
uint64_t flags; // Flags:
// Bit 0: set if callback function is provided.
// All other bits are undefined and must be 0.
uint64_t callback; // Address of the terminal callback function,
// (see "Terminal struct tag")
};
The presence of this tag enables support for 5-level paging, if available.
Identifier: 0x932f477032007e8f
This tag does not have extra members.
The presence of this tag tells the bootloader to add a random slide to the base address of the higher half direct map (HHDM).
struct stivale2_header_tag_slide_hhdm {
struct stivale2_tag tag; // Identifier: 0xdc29269c2af53d1d
uint64_t flags; // Flags:
// All bits are undefined and must be 0.
uint64_t alignment; // This value must be non-0 and must be aligned
// to 2MiB. It tells the bootloader what alignment
// the base address of the HHDM should have.
};
The presence of this tag tells the bootloader to unmap the first page of the virtual address space before passing control to the kernel, for architectures that support paging.
Identifier: 0x92919432b16fe7e7
This tag does not have extra members.
The presence of this tag enables support for booting up application processors.
struct stivale2_header_tag_smp {
struct stivale2_tag tag; // Identifier: 0x1ab015085f3273df
uint64_t flags; // Flags:
// bit 0: 0 = use xAPIC, 1 = use x2APIC (if available)
// All other bits are undefined and must be 0.
};
This tag reports to the kernel that the bootloader recognised the PMR flag in the main header and it has successfully mapped the kernel as per ELF segments.
It additionally reports back the array of ranges and their permissions as it was mapped by the bootloader. Ranges bases and sizes are at least 4KiB aligned.
struct stivale2_struct_tag_pmrs {
struct stivale2_tag tag; // Identifier: 0x5df266a64047b6bd
uint64_t entries; // Count of PMRs in following array
struct stivale2_pmr pmrs[]; // Array of PMR structs
};
The PMR struct looks as follows:
struct stivale2_pmr {
uint64_t base;
uint64_t length;
uint64_t permissions;
};
The permissions
field can have one or more of the following bits set, to determine
the range's permissions:
#define STIVALE2_PMR_EXECUTABLE ((uint64_t)1 << 0)
#define STIVALE2_PMR_WRITABLE ((uint64_t)1 << 1)
#define STIVALE2_PMR_READABLE ((uint64_t)1 << 2)
When fully virtual kernel mappings are not enabled, the physical address of a range
can be determined with the formula: PHYSICAL_ADDRESS = VIRTUAL_ADDRESS - 0xffffffff80000000
.
Otherwise, see the "kernel base address" structure tag.
This tag is returned only if the kernel requested the "fully virtual kernel mappings" feature, PMRs are enabled, and the bootloader supports the feature.
Unlike when fully virtual kernel mappings are disabled, there is no hard correspondence between the kernel's virtual load address and a physical address.
This tag returns to the kernel its base physical, and virtual load addresses, so that for each
PMR range, the corresponding physical address can be derived as such:
PHYSICAL_ADDRESS = PHYSICAL_BASE_ADDRESS + (VIRTUAL_ADDRESS - VIRTUAL_BASE_ADDRESS)
.
struct stivale2_struct_tag_kernel_base_address {
struct stivale2_tag tag; // Identifier: 0x060d78874a2a8af0
uint64_t physical_base_address;
uint64_t virtual_base_address;
};
This tag reports to the kernel the command line string that was passed to it by the bootloader.
struct stivale2_struct_tag_cmdline {
struct stivale2_tag tag; // Identifier: 0xe5e76a1b4597a781
uint64_t cmdline; // Pointer to a null-terminated cmdline
};
This tag reports to the kernel the memory map built by the bootloader.
struct stivale2_struct_tag_memmap {
struct stivale2_tag tag; // Identifier: 0x2187f79e8612de07
uint64_t entries; // Count of memory map entries
struct stivale2_mmap_entry memmap[]; // Array of memory map entries
};
struct stivale2_mmap_entry {
uint64_t base; // Physical address of base of the memory section
uint64_t length; // Length of the section
uint32_t type; // Type (described below)
uint32_t unused;
};
type
is an enumeration that can have the following values:
enum stivale2_mmap_type : uint32_t {
USABLE = 1,
RESERVED = 2,
ACPI_RECLAIMABLE = 3,
ACPI_NVS = 4,
BAD_MEMORY = 5,
BOOTLOADER_RECLAIMABLE = 0x1000,
KERNEL_AND_MODULES = 0x1001,
FRAMEBUFFER = 0x1002
};
All other values are undefined.
The kernel and modules loaded are not marked as usable memory. They are marked as Kernel/Modules (type 0x1001).
The entries are guaranteed to be sorted by base address, lowest to highest.
Usable and bootloader reclaimable entries are guaranteed to be 4096 byte aligned for both base and length.
Usable and bootloader reclaimable entries are guaranteed not to overlap with any other entry.
To the contrary, all non-usable entries (including kernel/modules) are not guaranteed any alignment, nor is it guaranteed that they do not overlap other entries.
This tag reports to the kernel the currently set up framebuffer details, if any.
struct stivale2_struct_tag_framebuffer {
struct stivale2_tag tag; // Identifier: 0x506461d2950408fa
uint64_t framebuffer_addr; // Address of the framebuffer
uint16_t framebuffer_width; // Width and height in pixels
uint16_t framebuffer_height;
uint16_t framebuffer_pitch; // Pitch in bytes
uint16_t framebuffer_bpp; // Bits per pixel
uint8_t memory_model; // Memory model: 1=RGB, all other values undefined
uint8_t red_mask_size; // RGB mask sizes and left shifts
uint8_t red_mask_shift;
uint8_t green_mask_size;
uint8_t green_mask_shift;
uint8_t blue_mask_size;
uint8_t blue_mask_shift;
uint8_t unused;
};
This tag reports to the kernel the currently set up CGA text mode details, if any.
struct stivale2_struct_tag_textmode {
struct stivale2_tag tag; // Identifier: 0x38d74c23e0dca893
uint64_t address; // Address of the text mode buffer
uint16_t unused; // Unused, must be 0
uint16_t rows; // How many rows
uint16_t cols; // How many columns
uint16_t bytes_per_char; // How many bytes make up a character
};
This tag provides the kernel with EDID information as acquired by the firmware.
struct stivale2_struct_tag_edid {
struct stivale2_tag tag; // Identifier: 0x968609d7af96b845
uint64_t edid_size; // The amount of bytes that make up the
// edid_information[] array
uint8_t edid_information[];
};
Note: This tag is deprecated and considered legacy. Use is discouraged and it may not be supported on newer bootloaders.
This tag exists if MTRR write-combining for the framebuffer was requested and successfully enabled.
Identifier: 0x6bc1a78ebe871172
This tag does not have extra members.
If a terminal was requested (see "Terminal header tag" above), and the feature
is supported, this tag is returned to the kernel to provide it with the physical
entry point of the stivale2_term_write()
function.
struct stivale2_struct_tag_terminal {
struct stivale2_tag tag; // Identifier: 0xc2b3f4c3233b0974
uint32_t flags; // Bit 0: cols and rows provided.
// Bit 1: max_length provided. If this bit is 0,
// assume a max_length of 1024.
// Bit 2: if callback was requested and the
// bootloader supports it, this bit is set.
// Bit 3: context control available.
// All other bits undefined and set to 0.
uint16_t cols; // Columns of characters of the terminal.
// Valid only if bit 0 of flags is set.
uint16_t rows; // Rows of characters of the terminal.
// Valid only if bit 0 of flags is set.
uint64_t term_write; // Physical pointer to the entry point of the
// stivale2_term_write() function.
uint64_t max_length; // If bit 1 of flags is set, this field exists
// and it specifies what the maximum allowed
// string length that can be passed to term_write() is.
// If max_length is 0, then there is no limit.
};
The C prototype of this function is the following:
void stivale2_term_write(uint64_t ptr, uint64_t length);
The calling convention matches the SysV C ABI for the specific architecture.
The function is not thread-safe, nor reentrant.
The callback is a function that is part of the kernel, which is called by the
terminal during a term_write()
call whenever an event or escape sequence cannot be
handled by the bootloader's terminal alone, and the kernel may want to be notified in
order to handle it itself.
Returning from the callback will resume the term_write()
call which will return
to its caller normally.
Not returning from a callback may leave the terminal in an undefined state and cause trouble.
The callback function must have the following prototype:
void stivale2_terminal_callback(uint64_t type, uint64_t, uint64_t, uint64_t);
The purpose of the last 3 arguments changes depending on the first, type
, argument.
The calling convention matches the SysV C ABI for the specific architecture.
The types are as follows:
STIVALE2_TERM_CB_DEC
- (type value:10
)
This callback is triggered whenever a DEC Private Mode (DECSET/DECRST) sequence
is encountered that the terminal cannot handle alone. The arguments to this
callback are: type
, values_count
, values
, final
.
values_count
is a count of how many values are in the array pointed to by values
.
values
is a pointer to an array of uint32_t
values, which are the values passed
to the DEC private escape.
final
is the final character in the DEC private escape sequence (typically l
or
h
).
STIVALE2_TERM_CB_BELL
- (type value:20
)
This callback is triggered whenever a bell event is determined to be necessary
(such as when a bell character \a
is encountered). The arguments to this
callback are: type
, unused1
, unused2
, unused3
.
STIVALE2_TERM_CB_PRIVATE_ID
- (type value:30
)
This callback is triggered whenever the kernel has to respond to a DEC private
identification request. The arguments to this callback are: type
, unused1
,
unused2
, unused3
.
STIVALE2_TERM_CB_STATUS_REPORT
- (type value40
)
This callback is triggered whenever the kernel has to respond to a ECMA-48
status report request. The arguments to this callback are: type
, unused1
,
unused2
, unused3
.
STIVALE2_TERM_CB_POS_REPORT
- (type value50
)
This callback is triggered whenever the kernel has to respond to a ECMA-48
cursor position report request. The arguments to this callback are: type
, x
,
y
, unused3
. Where x
and y
represent the cursor position at the time the
callback is triggered.
STIVALE2_TERM_CB_KBD_LEDS
- (type value60
)
This callback is triggered whenever the kernel has to respond to a keyboard
LED state change request. The arguments to this callback are: type
, led_state
,
unused2
, unused3
. led_state
can have one of the following values:
0, 1, 2, or 3
. These values mean: clear all LEDs, set scroll lock, set num lock,
and set caps lock LED, respectively.
STIVALE2_TERM_CB_MODE
- (type value:70
)
This callback is triggered whenever an ECMA-48 Mode Switch sequence
is encountered that the terminal cannot handle alone. The arguments to this
callback are: type
, values_count
, values
, final
.
values_count
is a count of how many values are in the array pointed to by values
.
values
is a pointer to an array of uint32_t
values, which are the values passed
to the mode switch escape.
final
is the final character in the mode switch escape sequence (typically l
or
h
).
STIVALE2_TERM_CB_LINUX
- (type value80
)
This callback is triggered whenever a private Linux escape sequence
is encountered that the terminal cannot handle alone. The arguments to this
callback are: type
, values_count
, values
, unused3
.
values_count
is a count of how many values are in the array pointed to by values
.
values
is a pointer to an array of uint32_t
values, which are the values passed
to the Linux private escape.
If context control is available, the term_write
function can additionally be
used to set and restore terminal context, and refresh the terminal fully.
In order to achieve these, special values for length
are passed.
These values are:
#define STIVALE2_TERM_CTX_SIZE ((uint64_t)(-1))
#define STIVALE2_TERM_CTX_SAVE ((uint64_t)(-2))
#define STIVALE2_TERM_CTX_RESTORE ((uint64_t)(-3))
#define STIVALE2_TERM_FULL_REFRESH ((uint64_t)(-4))
For CTX_SIZE
, the ptr
variable has to point to a location to which the terminal
will write a single uint64_t
which contains the size of the terminal context.
For CTX_SAVE
and CTX_RESTORE
, the ptr
variable has to point to a location
to which the terminal will save or restore its context from, respectively.
This location must have a size congruent to the value received from CTX_SIZE
.
For FULL_REFRESH
, the ptr
variable is unused. This routine is to be used after
control of the framebuffer is taken over and the bootloader's terminal has to
fully repaint the framebuffer to avoid inconsistencies.
Additionally, the kernel must ensure, when calling this routine, that:
-
Either the GDT provided by the bootloader is still properly loaded, or a custom GDT is loaded with at least the following descriptors in this specific order:
- Null descriptor
- 16-bit code descriptor. Base =
0
, limit =0xffff
. Readable. - 16-bit data descriptor. Base =
0
, limit =0xffff
. Writable. - 32-bit code descriptor. Base =
0
, limit =0xffffffff
. Readable. - 32-bit data descriptor. Base =
0
, limit =0xffffffff
. Writable. - 64-bit code descriptor. Base and limit irrelevant. Readable.
- 64-bit data descriptor. Base and limit irrelevant. Writable.
-
The currently loaded virtual address space is still the one provided at entry by the bootloader, or a custom virtual address space is loaded which identity maps the framebuffer memory region and all the bootloader reclaimable memory regions, with read, write, and execute permissions.
-
The routine is called by its physical address which should be identity mapped.
-
Bootloader-reclaimable memory entries are left untouched until after the kernel is done utilising bootloader-provided facilities (this terminal being one of them).
Notes regarding segment registers and FPU:
The values of the FS and GS segments are guaranteed preserved across the call. All other segment registers may have their "hidden" portion overwritten, but stivale2 guarantees that the "visible" portion is going to be restored to the one used at the time of call before returning.
No registers other than the segment registers and general purpose registers are going to be used. Especially, this means that there is no need to save and restore FPU, SSE, or AVX state when calling the terminal write function.
This service is not provided to IA-32 kernels.
It is guaranteed that the terminal be able to print the 7-bit ASCII character set,
that '\n'
(0x0a
) is the newline character that puts the cursor to the beginning of
the next line (scrolling when necessary), and that '\b'
(0x08
) is the backspace
character that moves the cursor over the previous character on screen.
All other expansions on this basic set of features are implementation specific.
Limine, the reference stivale2 implementation, supports a Linux console compatible terminal, which is a superset of a VT100 compatible terminal.
This tag lists modules that the bootloader loaded alongside the kernel, if any.
struct stivale2_struct_tag_modules {
struct stivale2_tag tag; // Identifier: 0x4b6fe466aade04ce
uint64_t module_count; // Count of loaded modules
struct stivale2_module modules[]; // Array of module descriptors
};
struct stivale2_module {
uint64_t begin; // Address where the module is loaded
uint64_t end; // End address of the module
char string[128]; // ASCII 0-terminated string passed to the module
// as specified in the config file
};
This tag reports to the kernel the location of the ACPI RSDP structure in memory.
struct stivale2_struct_tag_rsdp {
struct stivale2_tag tag; // Identifier: 0x9e1786930a375e78
uint64_t rsdp; // Pointer to the ACPI RSDP structure
};
This tag reports to the kernel the location of the SMBIOS entry points in memory.
struct stivale2_struct_tag_smbios {
struct stivale2_tag tag; // Identifier: 0x274bd246c62bf7d1
uint64_t flags; // Flags for future use. Currently unused and must be 0.
uint64_t smbios_entry_32; // 32-bit SMBIOS entry point address. 0 if unavailable.
uint64_t smbios_entry_64; // 64-bit SMBIOS entry point address. 0 if unavailable.
};
This tag reports to the kernel the current UNIX epoch, as per RTC.
struct stivale2_struct_tag_epoch {
struct stivale2_tag tag; // Identifier: 0x566a7bed888e1407
uint64_t epoch; // UNIX epoch at boot, read from system RTC
};
This tag reports to the kernel info about the firmware.
struct stivale2_struct_tag_firmware {
struct stivale2_tag tag; // Identifier: 0x359d837855e3858c
uint64_t flags; // Bit 0: 0 = UEFI, 1 = BIOS
};
This tag provides the kernel with a pointer to the EFI system table if available.
struct stivale2_struct_tag_efi_system_table {
struct stivale2_tag tag; // Identifier: 0x4bc5ec15845b558e
uint64_t system_table; // Address of the EFI system table
};
This tag provides the kernel with a pointer to a copy of the raw executable file of the kernel that the bootloader loaded.
struct stivale2_struct_tag_kernel_file {
struct stivale2_tag tag; // Identifier: 0xe599d90c2975584a
uint64_t kernel_file; // Address of the raw kernel file
};
This tag provides information about the raw kernel file that was loaded by the bootloader.
struct stivale2_struct_tag_kernel_file_v2 {
struct stivale2_tag tag; // Identifier: 0x37c13018a02c6ea2
uint64_t kernel_file; // Address of the raw kernel file
uint64_t kernel_size; // Size of the raw kernel file
};
This tag provides the GUID and partition GUID of the volume from which the kernel was loaded, if available.
The GUID is the GUID provided by the filesystem on the volume from which the kernel is loaded.
The partition GUID is the GUID associated to this volume, provided by the partition table, such as GPT.
struct stivale2_struct_tag_boot_volume {
struct stivale2_tag tag; // Identifier: 0x9b4358364c19ee62
uint64_t flags; // Bit 0: GUID is valid.
// Bit 1: Partition GUID is valid.
// All other bits undefined.
struct stivale2_guid guid; // GUID - valid if bit 0 of flags is set.
struct stivale2_guid part_guid; // Partition GUID - valid if bit 1 of flags is set.
};
struct stivale2_guid
is defined as such:
struct stivale2_guid {
uint32_t a;
uint16_t b;
uint16_t c;
uint8_t d[8];
};
This tag returns the slide that the bootloader applied over the kernel's load address as a positive offset.
struct stivale2_struct_tag_kernel_slide {
struct stivale2_tag tag; // Identifier: 0xee80847d01506c57
uint64_t kernel_slide; // Kernel slide
};
This tag reports to the kernel info about a multiprocessor environment.
struct stivale2_struct_tag_smp {
struct stivale2_tag tag; // Identifier: 0x34d1d96339647025
uint64_t flags; // Flags:
// bit 0: Set if x2APIC was requested and it
// was supported and enabled.
// All other bits are undefined and set to 0.
uint32_t bsp_lapic_id; // LAPIC ID of the BSP (bootstrap processor).
uint32_t unused; // Reserved for future use.
uint64_t cpu_count; // Total number of logical CPUs (including BSP)
struct stivale2_smp_info smp_info[]; // Array of smp_info structs, one per
// logical processor, including BSP.
};
struct stivale2_smp_info {
uint32_t processor_id; // ACPI Processor UID as specified by MADT
uint32_t lapic_id; // LAPIC ID as specified by MADT
uint64_t target_stack; // The stack that will be loaded in ESP/RSP
// once the goto_address field is loaded.
// This MUST point to a valid stack of at least
// 256 bytes in size, and 16-byte aligned.
// target_stack is an unused field for the
// struct describing the BSP.
uint64_t goto_address; // This field is polled by the started APs
// until the kernel on another CPU performs an
// atomic write to this field.
// When that happens, bootloader code will
// load up ESP/RSP with the stack value as
// specified in target_stack.
// It will then proceed to load a pointer to
// this very structure into either register
// RDI for 64-bit or on the stack for 32-bit,
// then, goto_address is called (a bogus return
// address is pushed onto the stack) and execution
// is handed off.
// The CPU state will be the same as described
// in "kernel entry machine state", with the exception
// of ESP/RSP and RDI/stack arg being set up as
// above.
// goto_address is an unused field for the
// struct describing the BSP.
uint64_t extra_argument; // This field is here for the kernel to use
// for whatever it wants. Writes here should
// be performed before writing to goto_address
// so that the receiving processor can safely
// retrieve the data.
// extra_argument is an unused field for the
// struct describing the BSP.
};
This tag reports that the kernel has been booted via PXE, and reports the server ip that it was booted from.
struct stivale2_struct_tag_pxe_server_info {
struct stivale2_tag tag; // Identifier: 0x29d1e96239247032
uint32_t server_ip; // Server ip in network byte order
};
This tag reports that there is a memory mapped UART port and its address. To write to this port, write the character, zero extended to a 32 bit unsigned integer to the address provided.
struct stivale2_struct_tag_mmio32_uart {
struct stivale2_tag tag; // Identifier: 0xb813f9b8dbc78797
uint64_t addr; // The address of the UART port
};
This tag describes a device tree blob for the platform.
struct stivale2_struct_tag_dtb {
struct stivale2_tag tag; // Identifier: 0xabb29bd49a2833fa
uint64_t addr; // The address of the dtb
uint64_t size; // The size of the dtb
};
This tag reports the start address of the higher half direct map (HHDM).
struct stivale2_struct_tag_hhdm {
struct stivale2_tag tag; // Identifier: 0xb0ed257db18cb58f
uint64_t addr; // Beginning of the HHDM (virtual address)
};